#define _WIN32_WINNT 0x0501

#include "console-common.h"
#include <SDL/SDL.h>
#include <iostream>
#include <cstddef>
#include <cstring>
#include <string>
#include <queue>
#include <windows.h>
#include <cmath>
#define __EVENT_CPP__
#include "lua-conwin.h"
#undef __EVENT_CPP__
#include "lua-conwin-altmode.h"
#include "lua-debug-funcs.h"
#include "lua-extra-attr.h"
#include "lua-render-ex.h"

// #define MTOS_EXPAND(str) #str
// #define MTOS(str) MTOS_EXPAND(str)

extern "C" int32_t luaL_tobit(lua_State*, int);

std::queue <std::string> lua_alt_proc_func_queue;

lua_State * (luacon_substates[LUA_SUBSTATES_COUNT]);

int luaL_optint_gfx_i(lua_State * L, int n);

#define ref luacon_alt_func_ref_0
#define hndl luacon_alt_func_hndl_0
#define altf luaevent_altkey_flags
#define swap2_if(c,a,b,d,e) if(c)swap_int(a,b),swap_int(d,e);

#if defined(__GNUC__) || defined(_MSC_VER)
__declspec(noinline)
#endif
void CriticalSectionInterrupt (HANDLE h, CRITICAL_SECTION * cs)
{
	ResetEvent(h);
	LeaveCriticalSection(cs);
	WaitForSingleObject(h, INFINITE);
	EnterCriticalSection(cs);
}

int ref, hndl;

static const char short_usage[] = "Enter=Select Alt=Cancel Double-Esc=Quit";

void luacon_alt_exec_cmd_thread (lua_State * L);
static void __exec_cmd (const char* cmd);

// static CONTEXT __prev_reg_data_0, __prev_reg_data_1;

/*
static void __luaevent_save_regs ()
{
	
}

static void __luaevent_restore_regs ()
{
	
}
*/

#define N_EVENTS 2

CRITICAL_SECTION luacon_critical_0, luacon_critlib_lock_0;
static HANDLE __luaQ_msg_event_[N_EVENTS];

void luacon_initCriticals ()
{
	InitializeCriticalSection(&luacon_critical_0);
	InitializeCriticalSection(&luacon_critlib_lock_0);
	for (int i = 0; i < N_EVENTS; i++)
		__luaQ_msg_event_[i] = CreateEvent(NULL, true, false, NULL);
}

typedef int8_t __MESSAGE_QTYPE;

typedef struct ___MESSAGE_QUEUE {
	struct ___MESSAGE_QUEUE *next;
	int prior;
	__MESSAGE_QTYPE type;
	size_t len;
	void* message;
} __MESSAGE_QUEUE, *__PMESSAGE_QUEUE;

static __PMESSAGE_QUEUE __luaQ_msg_front = (__PMESSAGE_QUEUE)NULL;

static int __luacon_pushmessage (const void* msg, int prior, size_t len, __MESSAGE_QTYPE type = 0)
{
	// EnterCriticalSection(&luacon_critical_0);
	__PMESSAGE_QUEUE ptr1, ptr2, ptr3 = __luaQ_msg_front;
	bool not_first = false;

	while (ptr3 != NULL && ptr3->prior >= prior)
		ptr1 = ptr3, ptr3 = ptr1->next, not_first = true;

	ptr2 = (__PMESSAGE_QUEUE)calloc(1, sizeof(__MESSAGE_QUEUE));
	if (ptr2 != NULL)
	{
		void* s = malloc(len);
		if (s != NULL)
		{
			if (not_first)
				ptr1->next = ptr2;
			else
				__luaQ_msg_front = ptr2;
			memcpy(s, msg, len);
			ptr2->next = ptr3;
			ptr2->prior = prior;
			ptr2->type = type;
			ptr2->len = len;
			ptr2->message = s;
			SetEvent(__luaQ_msg_event_[0]);
		}
		else
			free(ptr2);
	}
	// LeaveCriticalSection(&luacon_critical_0);
	return 0;
}

int luaQ_pushmessage (lua_State * L)
{
	size_t len;
	const char* msg = luaL_checklstring(L, 1, &len);
	int prior = lua_tointeger(L, 2);
	return __luacon_pushmessage(msg, prior, len);
}

int luaQ_pushmessagei (lua_State * L)
{
	int args = lua_gettop(L);
	size_t sz = args * sizeof(lua_Integer);
	lua_Integer *arr = (lua_Integer*)malloc(sz);
	for (int i = 0; i < args; i++)
		arr[i] = lua_tointeger(L, i + 1);
	__luacon_pushmessage(arr, 0, sz, 1);
	free(arr);
	return 0;
}

int luacon_popmessage (lua_State * L)
{
	EnterCriticalSection(&luacon_critical_0);
	int retargs;
retry:
	if (__luaQ_msg_front != NULL)
	{
		__PMESSAGE_QUEUE next = __luaQ_msg_front->next;
		void* msg = __luaQ_msg_front->message;
		switch (__luaQ_msg_front->type)
		{
		case 0:
			lua_pushlstring(L, (char*)msg, __luaQ_msg_front->len);
			retargs = 1;
			break;
		case 1:
			{
				int len = __luaQ_msg_front->len / sizeof(lua_Integer), i;
				if (!lua_checkstack(L, len))
					return LeaveCriticalSection(&luacon_critical_0),
						luaL_error(L, "stack overflow");
				lua_Integer *arr = (lua_Integer*)msg;
				retargs = len;
				for (i = 0; i < len; i++)
					lua_pushinteger(L, arr[i]);
			}
			break;
		}
		free(__luaQ_msg_front); free(msg);
		__luaQ_msg_front = next;
	}
	else
	{
		bool w = lua_toboolean(L, 1);
		if (w)
		{
			CriticalSectionInterrupt(__luaQ_msg_event_[0], &luacon_critical_0);
			goto retry;
		}
		lua_pushnil(L);
	}
	LeaveCriticalSection(&luacon_critical_0);
	return retargs;
}

int luadbg_icebp (lua_State * l)
{
#if defined(X86) && defined(__GNUC__)
	BOOL isdebugging;
	CheckRemoteDebuggerPresent(GetCurrentProcess(), &isdebugging);
	if (isdebugging)
		__asm__ __volatile__ (".byte 0xF1, 15, 31, 64, 0"); // int1 + 4 byte nop
#else
	return luaL_error(l, "doesn't support console.icebp function");
#endif
	return 0;
}

int luadbg_open_cmd_shell (lua_State * l)
{
	STARTUPINFO si = {sizeof(si)} ;  
	PROCESS_INFORMATION pi ;  
	TCHAR szCommandLine[MAX_PATH];  
	GetEnvironmentVariable(TEXT("ComSpec"), szCommandLine, MAX_PATH);
	CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);  
	return 0;
}

void scroll_dn (int32_t* buf, int lines)
{
	int i, j, len;
	j = XCHRS*lines*(SSKIP/4);
	len = XCHRS*YCHRS*(SSKIP/4) - j;
	memmove(buf, buf + j, len > 0 ? 4*len : 0);
	for (i = len; i < XCHRS*YCHRS*(SSKIP/4); i += (SSKIP/4))
	{
		buf[i  ] = 0x00;
		buf[i+2] = curr_bg;
	}	
}

void clear_line (int32_t* buf, int line_n, int lines)
{
	for (int i = line_n*XCHRS*(SSKIP/4), j = i+lines*XCHRS*(SSKIP/4); i < j; i += (SSKIP/4))
	{
		buf[i  ] = 0x00;
		buf[i+2] = curr_bg;
	}
}

void drawchr (int32_t* buf, int i, int c)
{
	i *= (SSKIP/4);
	buf[i  ] = c;
	buf[i+1] = curr_fg;
	buf[i+2] = curr_bg;
}

void drawchr (int32_t* buf, int x, int y, int c)
{
	int i = (SSKIP/4)*(y*(XCHRS)+x);
	buf[i  ] = c;
	buf[i+1] = curr_fg;
	buf[i+2] = curr_bg;
}

void drawchr_inv (int32_t* buf, int x, int y, int c)
{
	int i = (SSKIP/4)*(y*(XCHRS)+x);
	buf[i  ] = c;
	buf[i+1] = curr_bg;
	buf[i+2] = curr_fg;
}

#define drawchr_inbound(b,x,y,c) {if(IN_CONSOLE(x,y))drawchr(buf,x,y,c);}

static void __console_draw_line(int32_t* buf, int x1, int y1, int x2, int y2, int c)
{
	bool reverseXY = abs(y2-y1)>abs(x2-x1);
	int x, y, dx, dy, sy, e;
	swap2_if(reverseXY,x1,y1,x2,y2)
	swap2_if(x1 > x2,x1,x2,y1,y2)
	dx = x2 - x1;
	dy = -abs(y2 - y1);
	e = dx >> 1;
	y = y1;
	sy = (y1<y2) ? 1 : -1;
	for (x=x1; x<=x2; x++)
	{
		if (reverseXY)
		     drawchr_inbound(buf,y,x,c)
		else drawchr_inbound(buf,x,y,c)
		e += dy;
		if (e <= 0)
			y += sy, e += dx;
	}
}

#define TO_BOUND(begin, end, max)\
	if (begin < 0) begin = 0;\
	if (end > (max)) end = (max);

#define TO_MINMAX(x, y)\
	if (x > y) swap_int(x, y);

static void __console_draw_rect(int32_t* buf, int x1, int y1, int w, int h, int c)
{
	int x, y, x2 = x1 + w, y2 = y1 + h;
	TO_MINMAX(x1, x2) TO_MINMAX(y1, y2)
	int xdata[2] = {x1, x2};
	int ydata[2] = {y1, y2};
	TO_BOUND(x1, x2, XCHRS) TO_BOUND(y1, y2, YCHRS)

	for (int i = 0; i < 2; i++)
	{
		if (xdata[i] >= 0 && xdata[i] < XCHRS)
			for (y = y1; y <= y2; y++)
				drawchr(buf, xdata[i], y, c);
		if (ydata[i] >= 0 && ydata[i] < YCHRS)
			for (x = x1; x <= x2; x++)
				drawchr(buf, x, ydata[i], c);
	}
}

static void __console_fill_rect(int32_t* buf, int x1, int y1, int w, int h, int c, int ix, int iy, int ish, int osh)
{
	int x2 = x1 + w, y2 = y1 + h;
	int i, j, k = osh;
	TO_MINMAX(x1, x2) TO_MINMAX(y1, y2)
	TO_BOUND(x1, x2, XCHRS) TO_BOUND(y1, y2, YCHRS)
	for (j = y1; j < y2; j += iy)
	{
		for (i = x1 + k; i < x2; i += ix)
			drawchr(buf, i, j, c);
		k += ish; if (k >= ix) k -= ix;
	}
}

static void __console_clear_rect(int32_t* buf, int x1, int y1, int w, int h)
{
	int i, y, o1, o2, x2 = x1 + w, y2 = y1 + h;
	TO_MINMAX(x1, x2) TO_MINMAX(y1, y2)
	TO_BOUND(x1, x2, XCHRS) TO_BOUND(y1, y2, YCHRS)
	o1 = (x1+(XCHRS)*y1) * (SSKIP/4);
	o2 = (x2 - x1) * (SSKIP/4);
	for (y = y1; y < y2; y ++, o1 += XCHRS*(SSKIP/4))
		for (i = o1; i < o1 + o2; i += (SSKIP/4))
			buf[i] = 0, buf[i+2] = curr_bg;

}

static void __console_fill_rect(int32_t* buf, int x1, int y1, int w, int h, int c)
{
	__console_fill_rect(buf, x1, y1, w, h, c, 1, 1, 0, 0);
}

static void __luaL_rect_general (lua_State * l, int args, void (*fn)(int32_t*, int, int, int, int, int))
{
	int c, x1, y1, x2, y2, p;
	if (args >= 5)
	{
		// argument: c, x, y, w, h, p
		luaL_va_tointegers (l, 1, &c, &x1, &y1, &x2, &y2, NULL);
		p = luaL_optint_gfx_i(l, 6);
	}
	else
	{
		luaL_error(l, "Must be least 5 arguments");
		return;
	}

	fn(semigraphics_bufs[p], x1, y1, x2, y2, c);
}

static inline int __console_str_align_pos (int a, int l)
{
	return (a > 0) ? l : (a == 0) ? (l >> 1) : 0;
}

static bool _DRAWSTRCHK0 (lua_State* L, int idx, int32_t* buf, int begin, int res, int align, bool dir = false)
{
	size_t len;
	const char* str = lua_tolstring(L, idx, &len); // input string
	if (str == NULL) return true; // check string is NULL
	int i, j, endsc = dir ? YCHRS : XCHRS;
	begin += __console_str_align_pos(align, -len);
	int end = len - (j = -begin);

	TO_BOUND(begin, end, endsc)

	if (dir)
		for (i = begin; i < end; i++)
			drawchr(buf, res, i, str[i+j] & 0xFF);
	else
		for (i = begin; i < end; i++)
			drawchr(buf, i, res, str[i+j] & 0xFF);
	return false;
}

int LuaConsoleGraphics::drawstr (lua_State * L)
{
	int args = lua_gettop(L); size_t len;
	int posx, posy, page,	// position x, y and window page
		endx, i, j;
	if (args < 3) return luaL_error(L, "Must be least 3 arguments");
	luaL_va_tointegers (L, 2, &posx, &posy, NULL);
	page = luaL_optint_gfx_i(L, 5);
	int32_t* buf = semigraphics_bufs[page];
	switch (lua_type(L, 1))
	{
	case LUA_TSTRING:
	case LUA_TNUMBER:
		if (posy >= 0 && posy < YCHRS)
			_DRAWSTRCHK0(L, 1, buf, posx, posy, luaL_optint(L, 4, -1));
		break;
	case LUA_TTABLE:
		{
			int k = 1-posy, a = luaL_optint(L, 4, -1);
			if (posy < 0) posy = 0;
			for (; posy < YCHRS; posy++)
			{
				lua_rawgeti(L, 1, k+posy);
				if (_DRAWSTRCHK0(L, -1, buf, posx, posy, a)) break;
				lua_pop(L, 1);
			}
		}
		break;
	default:
		return luaL_error(L, "argument #1 must be string or table");
	}
	return 0;
}

int LuaConsoleGraphics::drawstr_vertical (lua_State * L)
{
	int args = lua_gettop(L); size_t len;
	int posx, posy, page,	// position x, y and window page
		endy, i, j;
	if (args < 3) return luaL_error(L, "Must be least 3 arguments");
	luaL_va_tointegers (L, 2, &posx, &posy, NULL);
	page = luaL_optint_gfx_i(L, 5);
	int32_t* buf = semigraphics_bufs[page];
	switch (lua_type(L, 1))
	{
	case LUA_TSTRING:
	case LUA_TNUMBER:
		if (posx >= 0 && posx < XCHRS)
			_DRAWSTRCHK0(L, 1, buf, posy, posx, luaL_optint(L, 4, -1), true);
		break;
	case LUA_TTABLE:
		{
			int k = 1-posx, a = luaL_optint(L, 4, -1);
			if (posx < 0) posx = 0;
			for (; posx < YCHRS; posx++)
			{
				lua_rawgeti(L, 1, k+posx);
				if (_DRAWSTRCHK0(L, -1, buf, posy, posx, a, true)) break;
				lua_pop(L, 1);
			}
		}
		break;
	default:
		return luaL_error(L, "argument #1 must be string or table");
	}
	return 0;
}

#undef ENUMPOS_0

int LuaConsoleGraphics::drawline (lua_State * l)
{
	__luaL_rect_general(l, lua_gettop(l), __console_draw_line);
	return 0;
}

int LuaConsoleGraphics::drawrect (lua_State * l)
{
	__luaL_rect_general(l, lua_gettop(l), __console_draw_rect);
	return 0;
}

int LuaConsoleGraphics::clearrect (lua_State * L)
{
	int x, y, w, h, p, args = lua_gettop(L);
	if (args < 4) return luaL_error (L, "Must be least 4 arguments");
	luaL_va_tointegers (L, 1, &x, &y, &w, &h, NULL);
	p = luaL_optint_gfx_i(L, 5);
	__console_clear_rect(semigraphics_bufs[p], x, y, w, h);
	return 0;
}

int LuaConsoleGraphics::fillrect (lua_State * L)
{
	int args = lua_gettop(L);
	if (args >= 7)
	{
		int p, x, y, w, h, ch, m, n, sh1 = 0, sh2 = 0;
		luaL_va_tointegers (L, 1, &ch, &x, &y, &w, &h, &m, &n, NULL);
		p = luaL_optint_gfx_i(L, 10);
		if (m <= 0 || n <= 0)
			return luaL_error(L, "Density must be positive");
		if (args >= 8)
			sh1 = lua_tointeger(L, 8) % m, (sh1 < 0) && (sh1 += m),
			sh2 = lua_tointeger(L, 9) % m, (sh2 < 0) && (sh2 += m);
		__console_fill_rect(semigraphics_bufs[p], x, y, w, h, ch, m, n, sh1, sh2);
	}
	else
		__luaL_rect_general(L, args, __console_fill_rect);
	return 0;
}

void drawchr_hash(int32_t * buf, int x, int y, int i)
{
	int o = (y*(XCHRS)+x);
	if (buf[2*o+1] == -1) buf[2*o+1] = buf[0], buf[0] = o;
	buf[2*o+2] |= i;
}

#if defined(X86) && defined(__GNUC__) && !(defined(_64BIT) || defined(_WIN64))
__attribute__((regparm(3)))
#endif
static bool __patt_decode_handler_0 (int32_t * b, unsigned char * &s, void * eos, int cx, int cy, int it = 1)
{
	int l, tmp_x = cx;
	static const char v1[] = {30,36,42,48,0,18,24,6,12,'[','\\',']','\''};
	unsigned char id, k;
	
	for (; s < eos; s++)
	{
		if (*s >= 60 && *s <= 126)
		{
			if (*s != 126)
			{
				id = 126 - *s, ((id >= 64) && (id -= 31));
				while (id)
				{
					k = __builtin_ctz(id);
					id &= (id - 1);
					l = cy + k;
					if (IN_CONSOLE(cx, l)) drawchr_hash(b, cx, l, it);
				}
			}
			cx ++;
		}
		else switch (*s)
		{
		case ':': case ';': case '(': case ')':
			cy += (int)(v1[(*s & 0xF) - 3]);
			if (s[1] == '(' || s[1] == ')' || s[1] == ':' || s[1] == ';' || (s[1] >= '#' && s[1] <= '&'))
				s++, cy += 4 * (int)(v1[(*s & 0xF) - 3]);
			goto jmp1;
		case '#': case '$': case '%': case '&': // most case is not used
			if (k < '#' && k > '~') break;
			k = s[1];
			if (k >= 0x7B) k = v1[k - 0x7B + 9];
			cy += (*s + (k - '#') * 4 - '#' + 37) * 6; s++;
		jmp1:
			cx = tmp_x;
			if (cy > YCHRS)
				return true;
			break;
		case '!': case '/': return true;
		case '0': case '1': case '2': case '3': case '4': 
		case '5': case '6': case '7': case '8': case '9': 
			l = *s - '0';
			while (s[1] >= '0' && s[1] <= '9')
				l *= 10, s++, l += *s - '0';
			cx += l + 2;
			break;
		}
	}
	return false;
}

static int __patt_decode_unwind_0 (int32_t * b)
{
	int32_t l = b[0];
	while (l >= 0)
	{
		int32_t ll = 2*l+1;
		l = b[ll];
		b[ll] = -1;
		b[ll+1] = 0;
	}
	b[0] = -1;
	return 0;
}

int luacon_drawchr_patt_decode (lua_State * L)
{
	// Note: String literal "]]" or "]=" are used to Lua string terminator
	size_t sz;
	if (lua_gettop(L) < 3) return luaL_error(L, "Must be least 3 arguments");
	unsigned char * s = (unsigned char*)luaL_checklstring(L, 1, &sz);
	int32_t * buf = semigraphics_bufs[luaL_optint_gfx_i(L, 4)];
	int32_t * bufi = luacon_mask_table_0, l, ll, ch, it = 1;
	void * eos = (void*)((intptr_t)(s + sz));

	int x = lua_tointeger(L, 2);
	int y = lua_tointeger(L, 3);

	IN_CONSOLE(x, y) && (bufi[2*(y*(XCHRS)+x)] = -2);

	while (__patt_decode_handler_0(bufi, s, eos, x, y, it))
	{
		while (*s != '!' && *s != '/' && s < eos)
			s++;
		if (*s == '!') {
			sz = s[1] ? 1 : 0;
			s++; it <<= 1;
			continue;
		}
		else if (*s == '/') {
			sz = (intptr_t)eos - (intptr_t)s;
			l = bufi[0];
			while (l >= 0)
			{
				ll = 2*l+1; ch = bufi[ll+1];
				if (ch < (int32_t)sz) drawchr(buf, l, s[ch]);
				l = bufi[ll], bufi[ll] = -1, bufi[ll+1] = 0;
			}
			bufi[0] = -1;
			return 0;
		}
		break;
	}

	return __patt_decode_unwind_0(bufi); // "Unwinding" process
}

static void scrn_replace_str(int32_t * buf, int pos, int old_len, char * new_str, int new_len)
{
	int i = pos+new_len, j = pos+old_len;
	int mod_chars = input_chars + new_len - old_len;

	if (old_len != new_len)
	{
		for (; j < input_chars; i++, j++) // moving buffer
			input_buffer2[i] = input_buffer[j];

		for (i = pos+new_len; i < mod_chars; i++) // moving buffer back
			input_buffer[i] = input_buffer2[i];
	}

	for (i = 0; i < new_len; i++)
		input_buffer[pos+i] = new_str[i];

	if (-first_input_line * XCHRS > pos) pos = -first_input_line * XCHRS;

	for (j = pos; j < mod_chars; j++)
		drawchr (buf, j, first_input_line, input_buffer[j] & 0xFF);
	
	for (; j < input_chars; j++)
		drawchr (buf, j, first_input_line, 0);
}

static int __luacon_set_rect_a (lua_State * L, BLINK_FLAGS_TYPE * data)
{
	if (data == NULL) return 0;
	int m  = luaL_checkint(L, 1);
	int x1 = luaL_checkint(L, 2);
	int y1 = luaL_checkint(L, 3);
	int x2 = x1 + luaL_optinteger(L, 4, 1);
	int y2 = y1 + luaL_optinteger(L, 5, 1);
	TO_BOUND(x1, x2, XCHRS) TO_BOUND(y1, y2, YCHRS) // m &= ~BFLAG_DISABLE;
	
	for (int y = y1; y < y2; y++)
		for (int x = x1; x < x2; x++)
			data[y * (XCHRS) + x] = m;
	return 0;
}

int luacon_scrn_set_blink_a (lua_State * L)
{
	return __luacon_set_rect_a(L, sdl_blinking_flags_curr);
}

int luacon_scrn_set_blink_dec (lua_State * L)
{
	if (sdl_blinking_flags_curr == NULL)
		return 0;

	// Note: String literal "]]" or "]=" are used to Lua string terminator
	size_t sz;
	int x, y, m;
	if (lua_gettop(L) < 4) return luaL_error(L, "Must be least 4 arguments");
	unsigned char * s = (unsigned char*)luaL_checklstring(L, 1, &sz);
	int32_t * buf = luacon_mask_table_0, l, ll;
	void * eos = (void*)((intptr_t)(s + sz));

	x = lua_tointeger(L, 2);
	y = lua_tointeger(L, 3);
	m = lua_tointeger(L, 4);

	IN_CONSOLE(x, y) && (buf[2*(y*(XCHRS)+x)] = -2);

	__patt_decode_handler_0(buf, s, eos, x, y);
	l = buf[0];
	while (l >= 0)
	{
		ll = 2*l+1;
		sdl_blinking_flags_curr[l] = m;
		l = buf[ll], buf[ll] = -1, buf[ll+1] = 0;
	}
	buf[0] = -1;
	return 0;
}

int luacon_scrn_clear_blink (lua_State * L)
{
	if (sdl_blinking_flags_curr != NULL)
		memset(sdl_blinking_flags_curr, 0, XCHRS * YCHRS * sizeof(BLINK_FLAGS_TYPE));
	return 0;
}

/*
const char* ClipboardPull()
{
	if (OpenClipboard(NULL))
	{
		HANDLE cbuffer;
		char * glbuffer;

		cbuffer = GetClipboardData(CF_TEXT);
		glbuffer = (char*)GlobalLock(cbuffer);
		GlobalUnlock(cbuffer);
		CloseClipboard();
		return glbuffer;
	}
	return NULL;
}
*/

#define MAX_LEN (XCHRS-24)

static char __tmp_msg_buff0[MAX_LEN + 1];

int luaS_userinput (lua_State * L)
{
	EnterCriticalSection(&luacon_critical_0);
	altf |= ALT_MSG_PUSH2;
	luacon_altmenu_suspend_count ++;
	CriticalSectionInterrupt(__luaQ_msg_event_[1], &luacon_critical_0);
	lua_pushstring(L, __tmp_msg_buff0);
	luacon_altmenu_suspend_count --;
	LeaveCriticalSection(&luacon_critical_0);
	return 1;
}

#define clear_altcurs (altf &= ~ALT_HAS_CURSOR)
#define set_status(str, size) (clear_altcurs, memcpy(SDL_curr_status, str, size))
#define get_short_help (altf &= ~ALT_ERR_RUN1, set_status(short_usage, sizeof(short_usage)))
#define __ptr tmp_input_pos_0
#define altf_save_mask (0)

void console_altmode_call (bool altflag, char * cmd)
{
	static int prev_fg, prev_bg, tmp_altf;
	
	if (altflag)
	{
		if (altf & ALT_FUNC_RUNNING)
		{
			altf |= ALT_FUNC_LOCKING;
			WaitForSingleObject((HANDLE)hndl, INFINITE);
		}
		altf &= ~altf_save_mask;
		altf |= tmp_altf;
		console_free_scrn(gfx_index);
		gfx_index = tmp_gfx_index_2;
		curr_fg = prev_fg, curr_bg = prev_bg;
		clear_altcurs;

		sdl_blinking_flags_curr = sdl_blinking_flags_0;

		// __luaevent_restore_regs();
		ResumeThread(ScriptThreadHandle.h);
	}
	else
	{
		SuspendThread(ScriptThreadHandle.h);
		// __luaevent_save_regs();
		tmp_gfx_index = tmp_gfx_index_2 = gfx_index;
		prev_fg = curr_fg, prev_bg = curr_bg;
		gfx_index = console_alloc_scrn (gfx_index);
		tmp_altf = altf & altf_save_mask;
		altf &= ~altf_save_mask;
		// altf |=
		recreate_window_menu(true);
		__ptr = 0; get_short_help;
		(cmd != NULL) && (cmd[0] = 0);
	}
}

static bool __esc_pressed = false;
static int8_t __esc_pressed_hist = 0;
static int8_t __esc_pressed_delay = 0;

void sdl_decrement_timeout()
{
	if (__esc_pressed_delay)
		__esc_pressed_delay --;
}

bool SDL_console_key_enter_0(int sdl_key, uint16_t unicode, uint16_t mod, int* _quit, char* cmd)
{
	int8_t esc_pressed_delay_p = __esc_pressed_delay;
	{
		int8_t m = 0; // t = 0;
		__esc_pressed_hist <<= 2;
		if (sdl_key != SDLK_ESCAPE)
			m = 1;
		else
			m = (__esc_pressed ? 2 : 3);
		if (m == 3) __esc_pressed_delay = 20;
		__esc_pressed_hist |= m;
		__esc_pressed = (sdl_key == SDLK_ESCAPE);
	}

	int orig = tmp_gfx_index;
	bool changed = false, cmd_changed = false;
	if (unicode >= 0x20 && unicode <= 0x7E)
	{
		if (__ptr < MAX_LEN)
			cmd_changed = true,
			cmd[__ptr++] = unicode,
			cmd[__ptr] = 0;
	}
	else switch (sdl_key)
	{
	case SDLK_LEFT:
		if (tmp_gfx_index > 0)
			tmp_gfx_index--;
		break;
	case SDLK_RIGHT:
		if (tmp_gfx_index < (int)semigraphics_bufs.size() - 1)
			tmp_gfx_index++;
		break;
	case SDLK_HOME:
		tmp_gfx_index = 0;
		break;
	case SDLK_END:
		tmp_gfx_index = semigraphics_bufs.size() - 1;
		break;
	case SDLK_ESCAPE:
		if (((__esc_pressed_hist & 0x3F) == 0x33) && esc_pressed_delay_p)
			*_quit = true;
		if (cmd[0])
			cmd_changed = true, cmd[__ptr = 0] = 0;
		break;
	case SDLK_BACKSPACE:
		if (__ptr > 0)
			cmd_changed = true, cmd[--__ptr] = 0;
		break;
	case SDLK_RETURN:
	case SDLK_KP_ENTER:
		if (__ptr || (luacon_altmenu_suspend_count &&
			(altf & (ALT_MSG_PUSH1 | ALT_MSG_PUSH2)) == ALT_MSG_PUSH2 ))
		{
			SDL_curr_status[__ptr = 0] = 0;
			__exec_cmd(cmd); cmd[0] = 0;
			break;
		}
		if (!((gfx_index == tmp_gfx_index) || IS_FREED_BUFF(tmp_gfx_index, semigraphics_bufs)))
			tmp_gfx_index_2 = tmp_gfx_index;
		if (!luacon_altmenu_suspend_count)
			altf &= ~ALT_ENABLED,
			console_altmode_call(true, cmd);
		break;
	case SDLK_F1:
		get_short_help;
		break;
	case SDLK_F2:
		altf ^= ALT_MSG_PUSH1;
		break;
	}
	if (cmd_changed)
		altf &= ~ALT_ERR_RUN1,
		altf |= ALT_HAS_CURSOR,
		memcpy(SDL_curr_status, cmd, __ptr + 1);
	if (changed || (orig != tmp_gfx_index)) recreate_window_menu(false);
	return false;
}

bool console_event_process (int sdl_key, uint16_t unicode, uint16_t mod, int32_t * textbuf, bool entered, int * _quit, char * cmd) // insert input mode
{
	if (entered && (mod & KMOD_ALT) && enable_fullscreen_shortcut)
	{
		SDL_ShowCursor(fullscreened);
		fullscreened = !fullscreened;
		SDL_SetVideoMode(XRES, YRES, 32, SDL_SWSURFACE | (fullscreened ? SDL_FULLSCREEN : 0));
		return false;
	}

	int fl = altf;

	if (fl & ALT_ENABLED)
		return SDL_console_key_enter_0 (sdl_key, unicode, mod, _quit, cmd);
	else
	{
		__esc_pressed_hist = 0;
		if (~fl & ALT_ECHO_INPUT)
			return false;
	}

	int old_len = 0, new_len;
	char* new_str = NULL;
	char ichr[2] = {0, 0};
	bool nextcmd = false;
	
	if (unicode >= 0x20 && unicode <= 0x7E)
	{
		new_str = ichr, ichr[0] = (char)unicode;
	}
	else switch (sdl_key)
	{
	case SDLK_LEFT:
		if (input_pos > 0)
			input_pos --;
		break;
	case SDLK_BACKSPACE:
		if (input_pos > 0)
			input_pos --,
			old_len = 1;
		break;
	case SDLK_RIGHT:
		if (input_pos < input_chars)
			input_pos ++;
		break;
	case SDLK_HOME:
		input_pos = 0;
		break;
	case SDLK_END:
		input_pos = input_chars;
		break;
	case SDLK_ESCAPE:
		input_pos = 0;
		old_len = input_chars;
		break;
	default:
		nextcmd = entered;
	}
	
	new_len = new_str == NULL ? 0 : strlen(new_str);
	int modified_len = input_chars + new_len - old_len;
	
	if (modified_len >= XCHRS * (YCHRS-1))
	{
		modified_len = XCHRS * (YCHRS-1);
		new_len = modified_len + old_len - input_chars;
	}

	// std::cout << first_input_line << " ";

	int last_input_line = first_input_line + modified_len / XCHRS;
	int total_extra_lines = last_input_line - YCHRS + (nextcmd ? 2 : 1);
	if (total_extra_lines > 0)
	{
		first_input_line -= total_extra_lines;
		scroll_dn(textbuf, total_extra_lines);
	}

	// std::cout << first_input_line << " ";

	scrn_replace_str(textbuf, input_pos, old_len, new_str, new_len);

	// std::cout << first_input_line << std::endl;

	if (nextcmd)
	{
		first_input_line += input_chars / XCHRS + 1;
		input_pos = input_chars = 0;
		input_buffer[modified_len] = 0;
	}
	else
	{
		input_chars = modified_len;
		input_pos += new_len;
	}

	return entered;
}

void SDL_console_key_release(int sdl_key, uint16_t mod)
{
	__esc_pressed = false;
	__esc_pressed_hist <<= 2;
	// keycode = 0;
}

int luaevent_cleanup_handles (lua_State * L)
{
	int n = luacon_thread_handles.size(), i, j;
#ifdef DEBUG
	std::cout << "Total handles: " << n << std::endl;
#endif
	for (i = 0; i < n; i++)
	{
		j = *(luacon_thread_handles[i]);
#ifdef DEBUG
		std::cout << "Handle #" << i << " = " << j << std::endl;
#endif
		if (j != (int)MainThreadHandle)
			SuspendThread((HANDLE)j); // suspending thread to prevent "abort"
		CloseHandle((HANDLE)j);
	}
	luacon_thread_handles.clear();
	
	for (i = 0; i < LUA_SUBSTATES_COUNT; i++)
		if (luacon_substates[i] != NULL)
			lua_close(luacon_substates[i]),
			luacon_substates[i] = NULL;

	for (i = semigraphics_bufs.size() - 1; i >= 0; i--)
		if (!IS_FREED_BUFF(i, semigraphics_bufs))
			free(semigraphics_bufs[i]);

	semigraphics_bufs.resize(0);
	lua_close(L);
	
	for (int i = 0; i < N_EVENTS; i++)
	{
		CloseHandle(__luaQ_msg_event_[i]);
		// __luaQ_msg_event_[i] = NULL;
	}
	return 0;
}

// static HANDLE __last_handle = NULL;

#define push_top	{ int top = lua_gettop(L); {
#define pop_top		} lua_settop(L, top); }

void luacon_alt_exec_cmd_thread (lua_State * L)
{
	for (;;)
	{
		SuspendThread(MainThreadHandle);
		if (lua_alt_proc_func_queue.empty())
		{
		resume0:
			altf &= ~ALT_FUNC_RUNNING;
			int oldh = hndl;
			hndl = (int)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)luacon_alt_exec_cmd_thread, L, CREATE_SUSPENDED, NULL);
			CloseHandle((HANDLE)oldh);
			ResumeThread(MainThreadHandle);
			return;
		}
		ResumeThread(MainThreadHandle);

		std::string str = lua_alt_proc_func_queue.front();
		push_top
		lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
		lua_pushstring(L, str.c_str());
		int r = lua_pcall(L, 1, LUA_MULTRET, 0);
		int t = lua_gettop(L);
		const char* s;
		int m = altf ^ (r ? ~0 : 0); // maybe using CMOVcc?
		altf ^= m & ALT_ERR_RUN1;
		if (t > top)
		{
			s = lua_tostring(L, top + 1);
			if (s) set_status(s, MAX_LEN + 1);
		}
		if (r)
		{
			luacon_reg_clearupmem(L);
#ifdef DEBUG
			std::cerr << "Error code: " << r << std::endl;
			std::cerr << "Error detail: " << lua_tostring(L, -1) << std::endl;
#endif
		}
		pop_top

		lua_alt_proc_func_queue.pop();
		if (altf & ALT_FUNC_LOCKING)
		{
			while (!lua_alt_proc_func_queue.empty())
				lua_alt_proc_func_queue.pop();
			altf &= ~ALT_FUNC_LOCKING;
			goto resume0;
		}
	}
	return;
}

static void __exec_cmd (const char* cmd)
{
	if (altf & ALT_MSG_PUSH1)
	{
		__luacon_pushmessage(cmd, 0, strlen(cmd));
	}
	else if (altf & ALT_MSG_PUSH2)
	{
		strcpy(__tmp_msg_buff0, cmd);
		altf &= ~ALT_MSG_PUSH2;
		SetEvent(__luaQ_msg_event_[1]);
	}
	else if (altf & ALT_FUNC_FOUND)
	{
		lua_alt_proc_func_queue.push(std::string(cmd));
		if (~altf & ALT_FUNC_RUNNING)
			altf |= ALT_FUNC_RUNNING,
			ResumeThread ((HANDLE)hndl);
	}
	return;
}
